pub const EI_NIDENT: usize = 16;
pub const EI_MAG0: usize = 0;
pub const EI_MAG1: usize = 1;
pub const EI_MAG2: usize = 2;
pub const EI_MAG3: usize = 3;
pub const EI_CLASS: usize = 4;
pub const EI_DATA: usize = 5;
pub const EI_VERSION: usize = 6;
pub const EI_PAD: usize = 7;

pub const ELFMAG0: u8 = 0x7f;
pub const ELFMAG1: u8 = b'E';
pub const ELFMAG2: u8 = b'L';
pub const ELFMAG3: u8 = b'F';

pub const ELFCLASSNONE: u8 = 0;
pub const ELFCLASS32: u8 = 1;
pub const ELFCLASS64: u8 = 2;

pub const ELFDATANONE: u8 = 0;
pub const ELFDATA2LSB: u8 = 1;
pub const ELFDATA2MSB: u8 = 2;

pub const ET_NONE: u16 = 0;
pub const ET_REL: u16 = 1;
pub const ET_EXEC: u16 = 2;
pub const ET_DYN: u16 = 3;
pub const ET_CORE: u16 = 4;
pub const ET_LOPROC: u16 = 0xff00;
pub const ET_HIPROC: u16 = 0xffff;

pub const EM_NONE: u16 = 0;
pub const EM_M32: u16 = 1;
pub const EM_SPARC: u16 = 2;
pub const EM_386: u16 = 3;
pub const EM_68K: u16 = 4;
pub const EM_88K: u16 = 5;
pub const EM_860: u16 = 7;
pub const EM_MIPS: u16 = 8;
pub const EM_MIPS_R4_BE: u16 = 10;

pub const EV_NONE: u32 = 0;
pub const EV_CURRENT: u32 = 1;

pub fn e_type_name(t: u16) -> &'static str {
    match t {
        ET_NONE => "No file type",
        ET_REL => "Relocatable file",
        ET_EXEC => "Executable file",
        ET_DYN => "Shared object file",
        ET_CORE => "Core file",
        ET_LOPROC..=ET_HIPROC => "Processor-specific file",
        _ => "Reserved",
    }
}

pub fn e_machine_name(m: u16) -> &'static str {
    match m {
        EM_NONE => "No machine",
        EM_M32 => "AT&T WE 32100",
        EM_SPARC => "SPARC",
        EM_386 => "Intel Architecture",
        EM_68K => "Motorola 68000",
        EM_88K => "Motorola 88000",
        EM_860 => "Intel 80860",
        EM_MIPS => "MIPS RS3000 Big-Endian",
        EM_MIPS_R4_BE => "MIPS RS4000 Big-Endian",
        _ => "Reserved",
    }
}

pub fn e_data_name(d: u8) -> &'static str {
    match d {
        ELFDATA2LSB => "Little-Endian",
        ELFDATA2MSB => "Big-Endian",
        _ => "Invalid data encoding",
    }
}

pub fn e_class_name(c: u8) -> &'static str {
    match c {
        ELFCLASS32 => "32-bit",
        ELFCLASS64 => "64-bit",
        _ => "Invalid class",
    }
}

#[repr(C)]
#[allow(non_camel_case_types)]
#[derive(Clone, Debug)]
pub struct Elf32_Ehdr {
    pub e_ident: [u8; EI_NIDENT],
    pub e_type: u16,
    pub e_machine: u16,
    pub e_version: u32,
    pub e_entry: u32,
    pub e_phoff: u32,
    pub e_shoff: u32,
    pub e_flags: u32,
    pub e_ehsize: u16,
    pub e_phentsize: u16,
    pub e_phnum: u16,
    pub e_shentsize: u16,
    pub e_shnum: u16,
    pub e_shstrndx: u16,
}

#[repr(C)]
#[allow(non_camel_case_types)]
#[derive(Clone, Debug)]
pub struct Elf64_Ehdr {
    pub e_ident: [u8; EI_NIDENT],
    pub e_type: u16,
    pub e_machine: u16,
    pub e_version: u32,
    pub e_entry: u64,
    pub e_phoff: u64,
    pub e_shoff: u64,
    pub e_flags: u32,
    pub e_ehsize: u16,
    pub e_phentsize: u16,
    pub e_phnum: u16,
    pub e_shentsize: u16,
    pub e_shnum: u16,
    pub e_shstrndx: u16,
}

pub const SHN_UNDEF: u32 = 0;
pub const SHN_LORESERVE: u32 = 0xff00;
pub const SHN_LOPROC: u32 = 0xff00;
pub const SHN_HIPROC: u32 = 0xff1f;
pub const SHN_ABS: u32 = 0xfff1;
pub const SHN_COMMON: u32 = 0xfff2;
pub const SHN_HIRESERVE: u32 = 0xffff;

pub const SHT_NULL: u32 = 0;
pub const SHT_PROGBITS: u32 = 1;
pub const SHT_SYMTAB: u32 = 2;
pub const SHT_STRTAB: u32 = 3;
pub const SHT_RELA: u32 = 4;
pub const SHT_HASH: u32 = 5;
pub const SHT_DYNAMIC: u32 = 6;
pub const SHT_NOTE: u32 = 7;
pub const SHT_NOBITS: u32 = 8;
pub const SHT_REL: u32 = 9;
pub const SHT_SHLIB: u32 = 10;
pub const SHT_DYNSYM: u32 = 11;
pub const SHT_LOPROC: u32 = 0x70000000;
pub const SHT_HIPROC: u32 = 0x7fffffff;
pub const SHT_LOUSER: u32 = 0x80000000;
pub const SHT_HIUSER: u32 = 0x8fffffff;
pub const SHT_GNU_ATTR: u32 = 0x6ffffff5;
pub const SHT_GNU_HASH: u32 = 0x6ffffff6;
pub const SHT_GNU_verdef: u32 = 0x6ffffffd;
pub const SHT_GNU_verneed: u32 = 0x6ffffffe;
pub const SHT_GNU_versym: u32 = 0x6fffffff;

pub const SHF_WRITE: u32 = 1;
pub const SHF_ALLOC: u32 = 2;
pub const SHF_EXECINSTR: u32 = 4;
pub const SHF_MERGE: u32 = 0x10;
pub const SHF_STRINGS: u32 = 0x20;
pub const SHF_INFO_LINK: u32 = 0x40;
pub const SHF_LINK_ORDER: u32 = 0x80;
pub const SHF_OS_NONCONFORMING: u32 = 0x100;
pub const SHF_GROUP: u32 = 0x200;
pub const SHF_TLS: u32 = 0x400;
pub const SHF_MASKOS: u32 = 0x0f000000;
pub const SHF_MASKPROC: u32 = 0xf0000000;
pub const SHF_ORDERED: u32 = 0x4000000;
pub const SHF_EXCLUDE: u32 = 0x8000000;

#[repr(C)]
#[allow(non_camel_case_types)]
#[derive(Clone, Debug)]
pub struct Elf32_Shdr {
    pub sh_name: u32,
    pub sh_type: u32,
    pub sh_flags: u32,
    pub sh_addr: u32,
    pub sh_offset: u32,
    pub sh_size: u32,
    pub sh_link: u32,
    pub sh_info: u32,
    pub sh_addralign: u32,
    pub sh_entsize: u32,
}

#[repr(C)]
#[allow(non_camel_case_types)]
#[derive(Clone, Debug)]
pub struct Elf64_Shdr {
    pub sh_name: u32,
    pub sh_type: u32,
    pub sh_flags: u64,
    pub sh_addr: u64,
    pub sh_offset: u64,
    pub sh_size: u64,
    pub sh_link: u32,
    pub sh_info: u32,
    pub sh_addralign: u64,
    pub sh_entsize: u64,
}

pub fn sh_type_name(t: u32) -> &'static str {
    match t {
        SHT_NULL => "SHT_NULL",
        SHT_PROGBITS => "SHT_PROGBITS",
        SHT_SYMTAB => "SHT_SYMTAB",
        SHT_DYNSYM => "SHT_DYNSYM",
        SHT_STRTAB => "SHT_STRTAB",
        SHT_RELA => "SHT_RELA",
        SHT_HASH => "SHT_HASH",
        SHT_DYNAMIC => "SHT_DYNAMIC",
        SHT_NOTE => "SHT_NOTE",
        SHT_NOBITS => "SHT_NOBITS",
        SHT_REL => "SHT_REL",
        SHT_SHLIB => "SHT_SHLIB",
        SHT_LOPROC..=SHT_HIPROC => "SHT_PROC",
        SHT_LOUSER..=SHT_HIUSER => "SHT_USER",
        SHT_GNU_ATTR => "SHT_GNU_ATTR",
        SHT_GNU_HASH => "SHT_GNU_HASH",
        SHT_GNU_verdef => "SHT_GNU_verdef",
        SHT_GNU_verneed => "SHT_GNU_verneed",
        SHT_GNU_versym => "SHT_GNU_versym",
        _ => "SHT_UNKNOWN",
    }
}

pub fn sh_flags_name(t: u64) -> &'static str {
    match t as u32 {
        SHF_WRITE => "SHF_WRITE",
        SHF_ALLOC => "SHF_ALLOC",
        SHF_EXECINSTR => "SHF_EXECINSTR",
        SHF_MASKOS => "SHF_MASKOS",
        SHF_MASKPROC => "SHF_MASKPROC",
        SHF_MERGE => "SHF_MERGE",
        SHF_STRINGS => "SHF_STRINGS",
        SHF_INFO_LINK => "SHF_INFO_LINK",
        SHF_OS_NONCONFORMING => "SHF_OS_NONCONFORMING",
        SHF_GROUP => "SHF_GROUP",
        SHF_TLS => "SHF_TLS",
        SHF_ORDERED => "SHF_ORDERED",
        SHF_EXCLUDE => "SHF_EXCLUDE",
        _ => "SHF_UNKNOWN",
    }
}

#[repr(C)]
#[allow(non_camel_case_types)]
#[derive(Clone, Debug)]
pub struct Elf32_Sym {
    pub st_name: u32,
    pub st_value: u32,
    pub st_size: u32,
    pub st_info: u8,
    pub st_other: u8,
    pub st_shndx: u16,
}

#[repr(C)]
#[allow(non_camel_case_types)]
#[derive(Clone, Debug)]
pub struct Elf64_Sym {
    pub st_name: u32,
    pub st_info: u8,
    pub st_other: u8,
    pub st_shndx: u16,
    pub st_value: u64,
    pub st_size: u64,
}

pub const STB_LOCAL: u8 = 0;
pub const STB_GLOBAL: u8 = 1;
pub const STB_WEAK: u8 = 2;
pub const STB_LOPROC: u8 = 13;
pub const STB_HIPROC: u8 = 15;

pub const STT_NOTYPE: u8 = 0;
pub const STT_OBJECT: u8 = 1;
pub const STT_FUNC: u8 = 2;
pub const STT_SECTION: u8 = 3;
pub const STT_FILE: u8 = 4;
pub const STT_LOPROC: u8 = 13;
pub const STT_HIPROC: u8 = 15;

#[allow(non_snake_case)]
#[inline(always)]
pub fn ELF32_ST_BIND(info: u8) -> u8 {
    info >> 4
}

#[allow(non_snake_case)]
#[inline(always)]
pub fn ELF32_ST_TYPE(info: u8) -> u8 {
    info & 0x0f
}

#[allow(non_snake_case)]
#[inline(always)]
pub fn ELF32_ST_INFO(b: u8, t: u8) -> u8 {
    (b << 4) | (t & 0x0f)
}

#[repr(C)]
#[allow(non_camel_case_types)]
#[derive(Clone, Debug)]
pub struct Elf32_Phdr {
    pub ph_type: u32,
    pub ph_offset: u32,
    pub ph_vaddr: u32,
    pub ph_paddr: u32,
    pub ph_filesz: u32,
    pub ph_memsz: u32,
    pub ph_flags: u32,
    pub ph_align: u32,
}

#[repr(C)]
#[allow(non_camel_case_types)]
#[derive(Clone, Debug)]
pub struct Elf64_Phdr {
    pub ph_type: u32,
    pub ph_flags: u32,
    pub ph_offset: u64,
    pub ph_vaddr: u64,
    pub ph_paddr: u64,
    pub ph_filesz: u64,
    pub ph_memsz: u64,
    pub ph_align: u64,
}

pub const PT_NULL: u32 = 0;
pub const PT_LOAD: u32 = 1;
pub const PT_DYNAMIC: u32 = 2;
pub const PT_INTERP: u32 = 3;
pub const PT_NOTE: u32 = 4;
pub const PT_SHLIB: u32 = 5;
pub const PT_PHDR: u32 = 6;
pub const PT_TLS: u32 = 7;
pub const PT_LOOS: u32 = 0x60000000;
pub const PT_HIOS: u32 = 0x6fffffff;
pub const PT_LOPROC: u32 = 0x70000000;
pub const PT_HIPROC: u32 = 0x7fffffff;

pub const PF_X: u32 = 1;
pub const PF_W: u32 = 2;
pub const PF_R: u32 = 4;

pub fn ph_type_name(t: u32) -> &'static str {
    match t {
        PT_NULL => ".PT_NULL",
        PT_LOAD => ".PT_LOAD",
        PT_DYNAMIC => ".PT_DYNAMIC",
        PT_INTERP => ".PT_INTERP",
        PT_NOTE => ".PT_NOTE",
        PT_SHLIB => ".PT_SHLIB",
        PT_PHDR => ".PT_PHDR",
        PT_TLS => ".PT_TLS",
        PT_LOOS..=PT_HIOS => ".PT_OS",
        PT_LOPROC..=PT_HIPROC => ".PT_PROC",
        _ => ".PT_UNKNOWN",
    }
}

pub fn ph_flags_name(t: u32) -> &'static str {
    match t {
        PF_X => "PF_X",
        PF_W => "PF_W",
        PF_R => "PF_R",
        _ => "PF_UNKNOWN",
    }
}

#[repr(C)]
#[allow(non_camel_case_types)]
#[derive(Clone, Debug)]
pub struct Elf32_Rel {
    pub r_offset: u32,
    pub r_info: u32,
}

#[repr(C)]
#[allow(non_camel_case_types)]
#[derive(Clone, Debug)]
pub struct Elf32_Rela {
    pub r_offset: u32,
    pub r_info: u32,
    pub r_addend: i32,
}

#[repr(C)]
#[allow(non_camel_case_types)]
#[derive(Clone, Debug)]
pub struct Elf64_Rel {
    pub r_offset: u64,
    pub r_info: u64,
}

#[repr(C)]
#[allow(non_camel_case_types)]
#[derive(Clone, Debug)]
pub struct Elf64_Rela {
    pub r_offset: u64,
    pub r_info: u64,
    pub r_addend: i64,
}

#[repr(C)]
#[allow(non_camel_case_types)]
#[derive(Clone, Debug)]
pub struct Elf_GnuHash {
    pub gh_nbuckets: u32,
    pub gh_symndx: u32,
    pub gh_bmmask: u32,
    pub gh_bmshift: u32,
}

pub fn elf_hash(name: &[u8]) -> u32 {
    elf_hash_append(name, 0) as u32
}

pub fn elf_hash_append(name: &[u8], mut h: usize) -> usize {
    for c in name {
        h = h.wrapping_shl(4);
        h = h.wrapping_add(*c as usize);
        h ^= (h >> 24) & 0xf0;
    }
    h
}

pub fn gnu_hash(name: &[u8]) -> u32 {
    gnu_hash_append(name, 0) as u32
}

pub fn gnu_hash_append(name: &[u8], mut h: usize) -> usize {
    if h == 0 {
        h = 5381;
    }
    for c in name {
        h = h.wrapping_mul(33);
        h = h.wrapping_add(*c as usize);
    }
    h
}

#[repr(C)]
#[derive(Debug, Clone)]
#[allow(non_camel_case_types)]
pub struct Elf32_Dyn {
    pub d_tag: u32,
    pub d_val_ptr: u32,
}

#[repr(C)]
#[derive(Debug, Clone)]
#[allow(non_camel_case_types)]
pub struct Elf64_Dyn {
    pub d_tag: u64,
    pub d_val_ptr: u64,
}

const DT_NULL: u32 = 0;
const DT_NEEDED: u32 = 1;
const DT_PLTRELSZ: u32 = 2;
const DT_PLTGOT: u32 = 3;
const DT_HASH: u32 = 4;
const DT_STRTAB: u32 = 5;
const DT_SYMTAB: u32 = 6;
const DT_RELA: u32 = 7;
const DT_RELASZ: u32 = 8;
const DT_RELAENT: u32 = 9;
const DT_STRSZ: u32 = 10;
const DT_SYMENT: u32 = 11;
const DT_INIT: u32 = 12;
const DT_FINI: u32 = 13;
const DT_SONAME: u32 = 14;
const DT_RPATH: u32 = 15;
const DT_SYMBOLIC: u32 = 16;
const DT_REL: u32 = 17;
const DT_RELSZ: u32 = 18;
const DT_RELENT: u32 = 19;
const DT_PLTREL: u32 = 20;
const DT_DEBUG: u32 = 21;
const DT_TEXTREL: u32 = 22;
const DT_JMPREL: u32 = 23;
const DT_BIND_NOW: u32 = 24;
const DT_INIT_ARRAY: u32 = 25;
const DT_FINI_ARRAY: u32 = 26;
const DT_INIT_ARRAYSZ: u32 = 27;
const DT_FINI_ARRAYSZ: u32 = 28;
const DT_RUNPATH: u32 = 29;
const DT_FLAGS: u32 = 30;
const DT_ENCODING: u32 = 31;
const DT_PREINIT_ARRAY: u32 = 32;
const DT_PREINIT_ARRAYSZ: u32 = 33;
const DT_LOOS: u32 = 0x6000000D;
const DT_HIOS: u32 = 0x6FFFF000;
const DT_LOPROC: u32 = 0x70000000;
const DT_HIPROC: u32 = 0x7FFFFFFF;

pub fn d_tag_is_ptr(tag: u64) -> bool {
    match tag {
        3..=7 | 12..=13 | 25..=26 | 17 | 21 | 23 | 32 => true,
        _ => false,
    }
}
